/* 着色器 */
class Shader{
	constructor(gl, type, name, code){
		console.groupCollapsed("[Shader("+ name + "):" + type + "]");
		console.log("[Shader:("+ name + "):" + type + "]", code)
		console.groupEnd("[Shader("+ name + "):" + type + "]");
		this.gl = gl;
		this.name = name;
		this.type = type;
		this.code = code;
		
		const shader = gl.createShader(type); //新建一个用于装顶点字符串的着色器对象
		this.shader = shader;
		gl.shaderSource(shader, code); //加载代码字符串
		gl.compileShader(shader); //编译
		//创建失败报错
		if ( !gl.getShaderParameter(shader, gl.COMPILE_STATUS) )
			throw "[Shader.init(" + name + "):" + this.type + "] Compile Error:" + gl.getShaderInfoLog(shader);
	}
	
	//从元素生成
	static fromDOM(gl, type, query){
		const code = document.querySelector(query).textContent.trim();
		return new this(gl, type, code);
	}
	
	//从文件生成
	static async fromFile(gl, type, file){
		console.log("[Shader.fromFile]", file)
		const code = await (await fetch(file)).text();
		return new this(gl, type, code);
	}
}



/* 程序 */
class ShaderProgram{
	constructor(gl, name, vertShader, fragShader, opt={}){
		//初始化
		const {attributes=[], uniforms=[], initScript, renderScript} = opt;
		this.gl = gl;
		this.name = name;
		this.vertShader = vertShader;
		this.fragShader = fragShader;
		
		//程序
		const program = gl.createProgram(); //新建一个程序
		this.program = program;
		gl.attachShader(program, vertShader.shader);
		gl.attachShader(program, fragShader.shader); //分别附加两个已编译好的着色器对象
		gl.linkProgram(program); //链接两个附件加好的着色器程序
		//创建失败报错
		if ( !gl.getProgramParameter(program, gl.LINK_STATUS) )
			throw "[Program.init("+ name +")] Link Error: " + gl.getProgramInfoLog(program);
		
		//Attributes
		this.attributes = {}; //存储位置location
		for (const attri of attributes){
			if (typeof attri == "string"){ //字符串 自行查找位置
				const location = this.gl.getAttribLocation(this.program, name);
				if (location == null){ //未找到
					console.error("[Program.init(" + this.name + ")] Cannot Find Attribute:", name)
					continue;
				}
				
				this.attributes[name] = location; //存储
			}else{ //对象 已知位置
				this.attributes[attri.name] = attri.location;
			}
		}
		
		//Uniforms
		this.uniforms = {}; //存储位置location
		for (const name of uniforms){
			const location = this.gl.getUniformLocation(this.program, name);
			if (location == null){ //未找到
				console.error("[Program.init(" + this.name + ")] Cannot Find Uniform:", name)
				continue;
			}
			
			this.uniforms[name] = location; //存储
		}
		
		//Script
		this.scripts = {
			init:initScript,
			render: renderScript
		};
	}
	
	
	//开启程序的使用
	use(){
		this.gl.useProgram(this.program);
		return this;
	}
	
	//安全使用
	useSafe(func){
		//保存状态
		const usingProgram = this.gl.getParameter(this.gl.CURRENT_PROGRAM);
		//改变状态
		this.gl.useProgram(this.program);
		func();
		//恢复状态
		this.gl.useProgram(usingProgram);
		return this;
	}
	
	
	//设置Attribute变量
	setAttributeBuffer(name, buffer, opt={}){
		let location = this.attributes[name]; //读取保存的位置
		if (location == null){
			location = this.gl.getAttribLocation(this.program, name);
			if (location == null)
				throw "[Program.setAttributeBuffer(" + this.name + ")] Cannot Find:" + name + ":" + value;
			
			this.attributes[name] = location; //存储
			console.warn("[Program.setAttributeBuffer(" + this.name + ")] Unstored Attribute:", name)
		}
		
		const {
			size=2, //int, 1/2/3/4
			type=this.gl.FLOAT,
			normalize=false,
			stride=0,
			offset=0
		} = opt;
		
		buffer.bindSafe(this.gl.ARRAY_BUFFER, ()=>{ //先绑定需要的buffer
			this.gl.vertexAttribPointer(location, size, type, normalize, stride, offset); //将坐标值赋值给变量
			this.gl.enableVertexAttribArray(location); //开启变量值的使用
		});
		return this;
	}
	
	//设置Uniform变量
	setUniform(name, type, ...value){
		let location = this.uniforms[name]; //读取保存的位置
		if (location == null){
			location = this.gl.getUniformLocation(this.program, name);
			if (location == null)
				throw "[Program.setUniform(" + this.name + ")] Cannot Find:" + name + ":" + value;
			
			this.uniforms[name] = location; //存储
			console.warn("[Program.setUniform(" + this.name + ")] Unstored Uniform:", name)
		}
		this.gl[type](location, ...value);
		return this;
	}
	
	
	//初始化
	init(){
		this.use();
		eval(this.scripts.init);
		return this;
	}
	
	initSafe(){
		return this.useSafe(()=>{
			eval(this.scripts.init);
		});
	}
	
	//渲染循环
	render(){
		this.use();
		eval(this.scripts.render);
		return this;
	}
	
	renderSafe(){
		return this.useSafe(()=>{
			eval(this.scripts.render);
		});
	}
	
	
	//从文件生成
	static async fromFile(gl, file){
		console.log("[Program.fromFile]", file);
		const code = await (await fetch(file)).text();
		//console.log(code)
		const domParser = new DOMParser(); //解析XML
		const xmlDocument = domParser.parseFromString(code, "text/xml").children[0];
		//console.log(xmlDocument)
		
		console.groupCollapsed("[Program.fromFile]" + file);
		let programName,
			attributes=[],
			uniforms=[],
			codes={},
			scripts={};
		for (const child of xmlDocument.children){
			const name = child.nodeName.toLowerCase(); //节点名（小写）
			switch (name){
				case "name": {
					programName = child.textContent.trim();
					break;}
					
				case "define": {
					for (const grandchild of child.children){ //二级迭代
						const name = grandchild.nodeName.toLowerCase(); //节点名（小写）
						switch (name){
							case "attribute":
								attributes.push({
									name: grandchild.getAttribute("name"),
									location: grandchild.getAttribute("location")
								});
								break;
							
							case "uniform":
								uniforms.push(grandchild.getAttribute("name"));
								break;
								
							default:
								console.warn("[Program.fromFile(" + name + ")] Unknown NodeName from <define>:", name);
								break;
						}
					}
					break;}
					
				case "shader": {
					const type = child.getAttribute("type");
					codes[type] = child.textContent.trim();
					break;}
					
				case "script": {
					const type = child.getAttribute("type");
					scripts[type] = child.textContent.trim();
				break;}
				
				default:
					console.warn("[Program.fromFile(" + name + ")] Unknown NodeName from <(root)>:", name);
					break;
			}
		}
		
		if (!codes.vertex)
			throw "[Program.fromFile(" + this.name + ")] VertexShader is lost"
		if (!codes.fragment)
			throw "[Program.fromFile(" + this.name + ")] FragmentShader is lost"
		
		//创建着色器
		const vertexShader = new Shader(gl, gl.VERTEX_SHADER, programName, codes.vertex);
		const fragmentShader = new Shader(gl, gl.FRAGMENT_SHADER, programName, codes.fragment);
		const program = new this(gl, programName, vertexShader, fragmentShader, {
			attributes,
			uniforms,
			initScript: scripts.init,
			renderScript: scripts.render
		});
		
		console.table({attributes, uniforms, scripts})
		console.groupEnd("[Program.fromFile]" + file);
		
		return {
			vertexShader,
			fragmentShader,
			program
		};
	}
}